今天會學到的
今天我們要回到[Day 20] [make→cmake] 執行人生中第一個CmakeList 中的專案,並詳細解釋Cmakelist.txt 之中的內容,以及運作的方式
首先我們先執行:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
注意,這邊沒有使用 -G
的指令,代表沒有指定generator 為 makefile的或ninja所以系統在建置的時候會使用預設的generator,而在大部分的Linux 或是 macOS 的環境下,預設的generator會是 Unix Makefiles
所以在不指定generator的情況下就會產生makefile了
如果你習慣使用 ninja
的話就可以指定 generator
為 ninja
的generator,例如:
cmake -S . -B build -G "Ninja" -DCMAKE_BUILD_TYPE=Debug
下了上面的指令之後你就會看到 build/
裡產生的是build.ninja
而不是 Makefile
了
另外,如果你想要自動執行編譯,而不是產生ninja或是makefile的話,可以用下面的指令
cmake --build build
總整理:
可以參考 [Day 21] [Cmake]深入解析Cmake, CMakelist.txt, Makefile 的對應關係下面表格中的指令對照, -B build
指定 cmakelist.txt 產生的相關makefile或是執行檔都要在 build
的這個資料夾中, -S
代表source (Cmakelist.txt)的來源,如果沒有特別指定的話就是指定當前的目錄,-G
就可以回頭看看上面的文章解釋
cmake -S . -B build -G "Unix Makefiles" # 產生 Makefile
cmake -S . -B build -G "Ninja" # 產生 build.ninja
cmake --build build # 直接編譯(自動挑 make 或 ninja)
對了這邊要補充說明一下CMake
打在terminal的指令才會決定產生 Makefile/Ninja/VS/Xcode,而CMakeList.txt只有描述有哪些目標、來源、旗標、相依、安裝規則....
如果你還是不指定generator 下了 cmake -S . -B build -G "Ninja" -DCMAKE_BUILD_TYPE=Debug
的話,你就會看到terminal中出現下面的訊息,而專案資料夾出現一個新的build
資料夾
打開build/
的資料夾,你會看到下面的資料型態,裡面有包含一個CMakeFiles的資料夾以及
下面的檔案目錄,其中你可以看到產生一個Makefile
而產生的Makefile裏頭的內容如下,基本上你可以不必管這個Makefile內有什麼內容了,因為CMake都會自動幫你產生,想要編譯的方式其實就好好標註在CMakelist.txt內就行了。
# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 3.28
# Default target executed when no arguments are given to make.
default_target: all
.PHONY : default_target
.......
這時,我們就可以回頭來看看這個CMakelist內有什麼東西了
以下會逐行解釋Cmakelist.txt中的內容
cmake_minimum_required(VERSION 3.21)
指定此專案最低需要的 CMake 版本(3.21)。可避免在舊版 CMake 上因語法/功能不支援而出錯。
project(C_PROJ VERSION 1.0.0 LANGUAGES C)
宣告專案名稱為 C_PROJ,版本 1.0.0,並且啟用 C 語言支援。CMake 會據此偵測 C 編譯器並初始化專案變數。
# Export compile_commands.json for clangd/clang-tidy
註解:說明下面那行是為了輸出 compile_commands.json
(給 clangd/clang-tidy/IDE 使用)。
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
開啟輸出 compile_commands.json
。這份 JSON 會列出每個檔案的實際編譯指令,利於語意分析與靜態檢查。
# C standard
註解:下面設定 C 語言標準。
set(CMAKE_C_STANDARD 11)
指定 C11 為目標語言標準。
set(CMAKE_C_STANDARD_REQUIRED ON)
要求必須使用指定標準(不要 fallback 到較舊的 C99 等)。
# Library from src/dog_meme.c
註解:下面建立一個函式庫目標(由 src/dog_meme.c
組成)。
add_library(dog_meme STATIC
建立名為 dog_meme 的 target,型別為 STATIC(靜態庫)。在 Unix 會輸出 libdog_meme.a
。
src/dog_meme.c
指定此函式庫的來源檔(source file)。
)
結束 add_library()
。
target_include_directories(dog_meme
對 dog_meme 目標設定 include 路徑。
PUBLIC
指明此 include 路徑是 PUBLIC:
app
)也會自動繼承這個路徑。${CMAKE_SOURCE_DIR}/inc
指出實際的 include 目錄在專案根下的 inc/
。通常放標頭如 dog_meme.h
。
)
結束 target_include_directories(dog_meme ...)
。
# Executable from src/main.c
註解:下面建立可執行檔目標(由 src/main.c
組成)。
add_executable(app
建立名為 app 的可執行檔 target。
src/main.c
指定可執行檔的來源檔。
)
結束 add_executable()
。
target_link_libraries(app PRIVATE dog_meme)
讓 app 連結 dog_meme 函式庫。PRIVATE
表示此相依性不會再向後傳遞給別的 target(只影響 app 自己)。
# Target-scoped warnings/defines/includes
註解:接下來是對 app 這個 target 的專屬設定(警告、巨集、include)。
target_compile_options(app PRIVATE -Wall -Wextra -Wpedantic)
對 app 加上編譯器警告選項(GCC/Clang 常見組合)。PRIVATE
表示只套用在 app 本身。
target_compile_definitions(app PRIVATE APP_VERSION="1.0.0")
對 app 定義編譯期巨集 APP_VERSION="1.0.0"
;程式中可 printf("%s", APP_VERSION)
使用。
target_include_directories(app PRIVATE ${CMAKE_SOURCE_DIR}/inc)
再對 app 額外加入 inc/
為 include 路徑。
註:由於
dog_meme
已用 PUBLIC 曝露inc/
,理論上 app 會繼承到;這一行屬於「保險/顯式」寫法。
# Install rules
註解:下面是安裝規則。
install(TARGETS app RUNTIME DESTINATION bin)
定義安裝 app 可執行檔到 bin/
。之後可用:
cmake --install <build_dir> --prefix <安裝根目錄>
安裝結果會出現在 <安裝根目錄>/bin/app
install(DIRECTORY inc/ DESTINATION include)
定義安裝 inc/
目錄下的標頭檔到 include/
。安裝後會是 <安裝根目錄>/include/...
。